﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using log4net;
using Newtonsoft.Json;

namespace Mrb.Utils
{
    public class Configuration
    {
        private static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private IDictionary<string, string> _systemProperties;
        private IDictionary<string, string> _hostProperties;
        private IDictionary<string, string> _defaultProperties;
        private IDictionary<string, string> _userProperties;
        private IDictionary<string, string> _commandLineParameters;
        private IDictionary<string, string> _envProperties;
        private readonly IList<string> _flags = new List<string>();
        private readonly string _baseDirectory;

        public static Configuration CreateAndLoad()
        {
            var configuration = new Configuration();
            configuration.Load();
            return configuration;
        }

        public static Configuration CreateAndLoad(string baseDirectory)
        {
            var configuration = new Configuration(baseDirectory);
            configuration.Load();
            return configuration;
        }

        public Configuration()
            : this(AppDomain.CurrentDomain.BaseDirectory)
        {
        }

        public Configuration(string baseDirectory)
        {
            this._baseDirectory = baseDirectory;
        }

        public void Load()
        {
            _systemProperties = SystemProperties();
            _commandLineParameters = LoadPropertiesFromCommandLine();
            _defaultProperties = LoadPropertiesFromDirectory(@"Default");
            _defaultProperties["ProcessID"] = Process.GetCurrentProcess().Id.ToString();
            _hostProperties = LoadPropertiesFromDirectory(@"Host");
            _userProperties = LoadPropertiesFromFile(string.Format(@"User\{0}.cfg", Environment.UserName));
            _envProperties = EnvProperties();
            EnvProperties();
        }

        private IDictionary<string, string> SystemProperties()
        {
            var systemProperties = new Dictionary<string, string>();
            systemProperties["ProcessID"] = Process.GetCurrentProcess().Id.ToString();
            systemProperties["Platform"] = IntPtr.Size == 8 ? "x64" : "x86";
            systemProperties["Version"] = Version();
            return systemProperties;
        }

        private string Version()
        {
            var entryAssembly = Assembly.GetEntryAssembly();
            if (entryAssembly != null)
            {
                return entryAssembly.GetName().Version.ToString();
            }
            return "null";
        }

        private IDictionary<string, string> EnvProperties()
        {
            var env = GetEnv();
            if (env != null)
            {
                return LoadPropertiesFromFile(string.Format(@"Env\{0}.cfg", env));
            }
            return new Dictionary<string, string>();
        }

        private string GetEnv()
        {
            return Resolve("env", EnvResolvingPropertiesList, false);
        }

        public override string ToString()
        {
            var properties = new Dictionary<string, string>();
            _systemProperties.Keys.ForEach(k => properties[k] = _systemProperties[k]);
            _defaultProperties.Keys.ForEach(k => properties[k] = _defaultProperties[k]);
            _envProperties.Keys.ForEach(k => properties[k] = _envProperties[k]);
            _userProperties.Keys.ForEach(k => properties[k] = _userProperties[k]);
            _commandLineParameters.Keys.ForEach(k => properties[k] = _commandLineParameters[k]);
            if (properties.Count == 0)
            {
                return "EMTPY";
            }
            var padding = properties.Max(kvp => kvp.Key.Length) + 5;
            return string.Join("\n",
                               properties.OrderBy(kvp => kvp.Key).Map(
                                   kvp => string.Format("\t{0} {1}", kvp.Key.PadRight(padding), kvp.Value)).
                                   ToArray());
        }

        private IDictionary<string, string> LoadPropertiesFromCommandLine()
        {
            var dictionary = new Dictionary<string, string>();
            try
            {
                Environment.GetCommandLineArgs().Skip(1).ForEach(s =>
                {
                    var keyAndValue = s.Split('=');
                    var key = keyAndValue[0].Remove(0, 1).ToLower();
                    if (keyAndValue.Count() == 1)
                    {
                        _flags.Add(key);
                    }
                    dictionary[key] = keyAndValue.Count() == 2
                                          ? keyAndValue[1]
                                          : null;
                });
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
            return dictionary;
        }

        private IDictionary<string, string> LoadPropertiesFromDirectory(string relativePath)
        {
            var directory = @"{0}\Config\{1}\".Fmt(_baseDirectory, relativePath);
            if (!Directory.Exists(directory))
            {
                return new Dictionary<string, string>();
            }
            var files = Directory.GetFiles(directory);
            var dictionary = new Dictionary<string, string>();
            foreach (var file in files)
            {
                if (file.EndsWith(".cfg"))
                {
                    var properties = LoadPropertiesFromFile(file);
                    properties.ForEach(kvp => dictionary[kvp.Key] = kvp.Value);
                }
                else if (file.EndsWith(".json"))
                {
                    var properties = LoadJsonPropertiesFromFile(file);
                    properties.ForEach(kvp => dictionary[kvp.Key] = kvp.Value);
                }
            }
            return dictionary;
        }

        public static IDictionary<string, string> LoadJsonPropertiesFromFile(string file)
        {
            var properties = new Dictionary<string, string>();
            using(var sr = new StreamReader(file))
            {
                var text = sr.ReadToEnd();
                var jsonProperties = text.ParseJsonDictionary();
                foreach (var kvp in jsonProperties)
                {
                    properties[kvp.Key] = kvp.Value;
                }
            }
            return properties;
        }

        public IDictionary<string, string> LoadPropertiesFromFile(string filePath)
        {
            if (!File.Exists(filePath))
            {
                return new Dictionary<string, string>();
            }
            var properties = new Dictionary<string, string>();
            using (var sr = new StreamReader(filePath))
            {
                while (!sr.EndOfStream)
                {
                    var line = sr.ReadLine();
                    var split = line.Split(new[] {'='}, 2);
                    if (split.Count() == 2)
                    {
                        properties[split[0]] = split[1];
                    }
                }
            }
            return properties;
        }

        public bool HasFlag(string flag)
        {
            var lowerFlag = flag.ToLower();
            return _flags.Any(f => f.ToLower() == lowerFlag);
        }

        public bool IsDefined(string propetyName)
        {
            var lowerPropertyName = propetyName.ToLower();
            return
                PropertiesList.Any(p => ContainsNonEmpty(p, lowerPropertyName));
        }

        private bool ContainsNonEmpty(IDictionary<string, string> p, string lowerPropertyName)
        {
            return p.ContainsKey(lowerPropertyName) && !string.IsNullOrEmpty(p[lowerPropertyName]);
        }

        public string this[string propertyName]
        {
            get { return Resolve(propertyName); }
        }

        public DateTime ResolveDateTime(string propertyName)
        {
            return DateTime.Parse(Resolve(propertyName));
        }

        public bool ResolveBool(string propertyName)
        {
            if (IsDefined(propertyName))
            {
                try
                {
                    return bool.Parse(this[propertyName]);
                }
                catch
                {
                }
            }
            return false;
        }

        public string Resolve(string propertyName)
        {
            return Resolve(propertyName, PropertiesList, true);
        }

        private string Resolve(string propertyName, IEnumerable<IDictionary<string, string>> propertiesList,
                               bool throwOnError)
        {
            var lowerPropertyName = propertyName.ToLower();
            var first = propertiesList.FirstOrDefault(p => ContainsNonEmpty(p, lowerPropertyName));
            if (first != null)
            {
                return first[lowerPropertyName];
            }
            if (throwOnError)
            {
                throw new ArgumentException(string.Format("Property {0} was not defined", lowerPropertyName));
            }
            return null;
        }

        private IEnumerable<IDictionary<string, string>> EnvResolvingPropertiesList
        {
            get
            {
                yield return _commandLineParameters;
                yield return _hostProperties;
                yield return _userProperties;
                yield return _defaultProperties;
            }
        }

        private IEnumerable<IDictionary<string, string>> PropertiesList
        {
            get
            {
                yield return _systemProperties;
                yield return _commandLineParameters;
                yield return _hostProperties;
                yield return _userProperties;
                yield return _envProperties;
                yield return _defaultProperties;
            }
        }

        public int ResolveInt(string propertyName)
        {
            return int.Parse(this[propertyName]);
        }

        public TimeSpan ResolveTimeSpan(string propertyName)
        {
            return TimeSpan.Parse(this[propertyName]);
        }

        public static IEnumerable<string> ResolveFile(string fileName)
        {
            var strings = new List<string>();
            if (File.Exists(fileName))
            {
                using (var reader = new StreamReader(fileName))
                {
                    while (!reader.EndOfStream)
                    {
                        strings.Add(reader.ReadLine());
                    }
                }
            }
            return strings;
        }

        public IEnumerable<T> ParseJsonArray<T>(string key)
        {
            var jsonArray = this[key];
            return JsonConvert.DeserializeObject<IEnumerable<T>>(jsonArray);
        }

        public T ParseJsonObject<T>(string key)
        {
            var jsonObject = this[key];
            return JsonConvert.DeserializeObject<T>(jsonObject);
        }
    }
}
